package com.von.platform.impl.hibernate.orm;

import java.io.Serializable;
import java.util.Collection;
import java.util.List;


import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.transform.Transformers;
import org.hibernate.type.Type;
import org.vnl.platform.orm.Command;
import org.vnl.platform.orm.CommandParameter;
import org.vnl.platform.orm.IConnectionManager;
import org.vnl.platform.orm.IPersistenceManager;
import org.vnl.platform.orm.ORMTypes;
import org.vnl.platform.orm.PageResult;
import org.vnl.platform.orm.QueryCommand;
import org.vnl.platform.orm.TransactionErrCode;
import org.vnl.platform.orm.TransactionException;


public class HibernatePersistenceManager implements IPersistenceManager {

    private static final Logger logger = Logger.getLogger(HibernatePersistenceManager.class);
    
    private IConnectionManager cm;
    private boolean txnBegan;
    
    private static final ThreadLocal<Transaction> currentTxn = new ThreadLocal<Transaction>();
    private static final ThreadLocal<Boolean> rollbackOnly = new ThreadLocal<Boolean>();
    
    public HibernatePersistenceManager() {
        txnBegan = false;
    }

    @Override
    public void begin() {
        if (currentTxn.get() != null) {
            return;
        }
        Session session = (Session) cm.getCurrentSession();
        currentTxn.set(session.beginTransaction());
        rollbackOnly.set(false);
        txnBegan = true;
    }

    @Override
    public void commit() {
        if (currentTxn.get() == null) {
            throw new TransactionException(TransactionErrCode.TRANSACTION_NOT_BEGAN);
        }
        if (rollbackOnly.get()) {
            throw new TransactionException(TransactionErrCode.TRANSACTION_MUST_ROLLBACK);
        }
        currentTxn.get().commit();
        ((Session) cm.getCurrentSession()).close();
        currentTxn.set(null);
        txnBegan = false;
    }

    @Override
    public void rollback() {
        if (currentTxn.get() == null) {
            throw new TransactionException(TransactionErrCode.TRANSACTION_NOT_BEGAN);
        }
        currentTxn.get().rollback();
        ((Session) cm.getCurrentSession()).close();
        currentTxn.set(null);
        rollbackOnly.set(null);
        txnBegan = false;
    }

    @Override
    public void save(Command obj) {
        boolean txnCtrl = !txnBegan;
        if (txnCtrl) {
            this.begin();
        }
        Session session = (Session) cm.getCurrentSession();
        Query query = session.getNamedQuery(obj.getName());
        List<CommandParameter> params = obj.getCondition();
        for (CommandParameter param : params) {
            query.setParameter(param.getName(), param.getValue(), getHibernateType(param.getType())); 
        }
        try {
            query.executeUpdate();
        } catch (HibernateException e) {
            logger.error("Execute SQL Error!",e);
            if (txnCtrl) {
                try {
                    this.rollback();
                } catch (TransactionException e1) {}
            }
            throw new TransactionException(TransactionErrCode.ERR_SQL_EXCEPTION, new Object[] {e.getMessage()}, e);
        }
        if (txnCtrl) {
            try {
                this.commit();
            } catch (TransactionException e) {}
        }
    }

    private Type getHibernateType(ORMTypes type) {
        return HibernateTypes.valueOf(type.toString()).getHibernateType();
    }
    
    @Override
    public void create(Command obj) {
        boolean txnCtrl = !txnBegan;
        if (txnCtrl) {
            this.begin();
        }
        Session session = (Session) cm.getCurrentSession();
        try {
            session.save(obj.getEntity());
        } catch (HibernateException e) {
            logger.error("Execute SQL Error!",e);
            if (txnCtrl) {
                try {
                    this.rollback();
                } catch (TransactionException e1) {}
            }
            throw new TransactionException(TransactionErrCode.ERR_SQL_EXCEPTION, new Object[] {e.getMessage()}, e);
        }
        if (txnCtrl) {
            try {
                this.commit();
            } catch (TransactionException e) {}
        }
    }

    @Override
    public void update(Command obj) {
        boolean txnCtrl = !txnBegan;
        if (txnCtrl) {
            this.begin();
        }
        Session session = (Session) cm.getCurrentSession();
        try {
            session.saveOrUpdate(obj.getEntity());
        } catch (HibernateException e) {
            logger.error("Execute SQL Error!",e);
            if (txnCtrl) {
                try {
                    this.rollback();
                } catch (TransactionException e1) {}
            }
            throw new TransactionException(TransactionErrCode.ERR_SQL_EXCEPTION, new Object[] {e.getMessage()}, e);
        }
        if (txnCtrl) {
            try {
                this.commit();
            } catch (TransactionException e) {}
        }
    }

    @Override
    public void delete(Command obj) {
        boolean txnCtrl = !txnBegan;
        if (txnCtrl) {
            this.begin();
        }
        Session session = (Session) cm.getCurrentSession();
        try {
            session.delete(obj.getEntity());
        } catch (HibernateException e) {
            logger.error("Execute SQL Error!",e);
            if (txnCtrl) {
                try {
                    this.rollback();
                } catch (TransactionException e1) {}
            }
            throw new TransactionException(TransactionErrCode.ERR_SQL_EXCEPTION, new Object[] {e.getMessage()}, e);
        }
        if (txnCtrl) {
            try {
                this.commit();
            } catch (TransactionException e) {}
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public Collection<Object> query(QueryCommand obj) {
        Session session = (Session) cm.getCurrentSession();
        Query query = null;
        if (StringUtils.isBlank(obj.getName())) {
            if (StringUtils.isBlank(obj.getNativeSQL())) {
                throw new TransactionException(TransactionErrCode.ERR_SQL_MISSING);
            }
            query = session.createSQLQuery(obj.getNativeSQL());
        } else {
            query = session.getNamedQuery(obj.getName());
        }
        if (obj.getResultClass() != null) {
            query.setResultTransformer(Transformers.aliasToBean(obj.getResultClass()));
//            throw new TransactionException(TransactionErrCode.ERR_RESULT_CLASS_MISSING);
        }
        List<CommandParameter> params = obj.getCondition();
        for (CommandParameter param : params) {
            if (param.getType() != null) {
                query.setParameter(param.getName(), param.getValue(), getHibernateType(param.getType()));
            } else {
                query.setParameter(param.getName(), param.getValue());
            }
        }
        try {
            return query.list();
        } catch (HibernateException e) {
            logger.error("Execute SQL Error!",e);
            throw new TransactionException(TransactionErrCode.ERR_SQL_EXCEPTION, new Object[] {e.getMessage()},e);
        }
    }

    @Override
    public void setConnectionManager(IConnectionManager cm) {
        this.cm = cm;
    }

    @Override
    public Object getByKey(QueryCommand key) {
        Session session = (Session) cm.getCurrentSession();
        Object result = session.load(key.getResultClass(), (Serializable) key.getKey());
        try {
            return result;
        } catch (HibernateException e) {
            logger.error("Execute SQL Error!",e);
            throw new TransactionException(TransactionErrCode.ERR_SQL_EXCEPTION, new Object[] {e.getMessage()},e);
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    public PageResult queryPage(int currentPage, int pageSize, QueryCommand obj) {
        Session session = (Session) cm.getCurrentSession();
        Query query = null;
        if (StringUtils.isBlank(obj.getName())) {
            if (StringUtils.isBlank(obj.getNativeSQL())) {
                throw new TransactionException(TransactionErrCode.ERR_SQL_MISSING);
            }
            query = session.createSQLQuery(obj.getNativeSQL());
        } else {
            query = session.getNamedQuery(obj.getName());
        }
        if (obj.getResultClass() == null) {
            throw new TransactionException(TransactionErrCode.ERR_RESULT_CLASS_MISSING);
        }
        query.setResultTransformer(Transformers.aliasToBean(obj.getResultClass()));
        List<CommandParameter> params = obj.getCondition();
        for (CommandParameter param : params) {
            query.setParameter(param.getName(), param.getValue(), getHibernateType(param.getType())); 
        }
        try {
            ScrollableResults results = query.scroll();
            query.setFetchSize(currentPage*pageSize);
            query.setMaxResults(pageSize);
            results.last();
            int total = results.getRowNumber()+1;
            return new PageResult(currentPage, pageSize, total, query.list());
        } catch (HibernateException e) {
            logger.error("Execute SQL Error!",e);
            throw new TransactionException(TransactionErrCode.ERR_SQL_EXCEPTION, new Object[] {e.getMessage()},e);
        }
    }

    @Override
    public boolean isRollbackOnly() {
        Boolean f = rollbackOnly.get();
        if (f == null) {
            return false;
        }
        return f.booleanValue();
    }

    @Override
    public void setRollbackOnly(boolean v) {
        rollbackOnly.set(v);
    }

}
